package com.livemeter;


import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;

import com.livemeter.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;

public class MeterControl extends VerticalProgressBar
{
    private int countOfmsShownInGraph = 5 * 60 * 1000;
    private ArrayList<MeterValueVisual> meterValues = new ArrayList<MeterValueVisual>();
    private ArrayList<Topic> allTopics = new ArrayList<Topic>();
    private long lastShownTimems = 0;
    
    private int currentThumbValue = 50;
    private Path graphPercentilePath = new Path(); //used to draw the graph displaying curve for 90percentile and 10percentile
    private Path graphLocalValuesPath = new Path(); //used to draw the graph displaying curve for local meter values
    private Path graphAverageValuesPath = new Path(); //used to draw the graph displaying curve for average meter values
    
    private final int graphOffsetToTop = 15;
    private final int graphOffsetToBottom = 15;
    private final int graphOffsetToLeft = 5;
    private final int graphOffsetToRight = 5;
	
    private int[] topics_xAxisValues;//to cache scaled topic values
 
	private boolean currentlyUpdatingValues = false; //used to allow adding more meter values without updating
	
	//variables needed for drawing thumb
    private RectF    thumbRect 					= new RectF();
    private Paint    thumbPaintNormal  			= new Paint();
    private Paint    thumbPaintFocused 			= new Paint();
    private Paint    thumbPaintNormal_Stroke  	= new Paint();
    private Paint    thumbPaintFocused_Stroke   = new Paint();
	private Paint 	 paintBackground 			= new Paint(); 
	private Paint  	 paintGraphAxis 			= new Paint();
	private Paint    paintTopicCircles 			= new Paint();
	private Paint    paintAverageMeterCurve		= new Paint();
	private Paint    paintLocalMeterCurve		= new Paint();
	private Paint    paintThumbGraphMode		= new Paint();
	private Paint    paintInactiveThumbGraphMode= new Paint();
	private Paint    paintthumbGraphMode_Stroke = new Paint();
	private Paint    paintTopicText				= new Paint();
	
	private boolean  drawThumbFocused 			= false;
    private int		 thumbHeight;
    private int 	 thumbWidth;
    
	private float widthOneSign;
	private int   availableWidth; 
	private boolean userIsTouchingThumb = false;
	
    private int myWidth;
    private int myHeight;
    private boolean inReadOnlyMode = false;  //used for master-mode
    private int radiusTopicCircles = 5;
    private int radiusSmallThumb = 7;
    
    //fields used to display currently clicked topic
    private String currentTopicText = "";
    private boolean drawTopicText = false;
    private Handler topicTimer = new Handler(); //used to make topic text disappear after some time
    private final int topicShowTime = 10000; //milliseconds clicked topic is shown
    private float widthOneSymbolTopic = 0;
    private int offsetTopicTextToTop = 30;
    private DecimalFormat numberFormat = new DecimalFormat();
    private int offSetToCircle = 15;

    private int currentMeterValueYAxis = 0;
    private boolean historyModeActive = false;
    private float oldTouchX = -1;  //old touch coordinates, needed to switch time in history view
    private float history_oldTouchX = -1;
    private int differenceTouch = 1;
    
    private OnClickListener graphClicked = null;
    
    /**On touch, this offset plus the scaled value from the position of the
     * touch will form the progress value. Usually 0.
     */
    float mTouchProgressOffset;

    /**
     * Whether this is user seekable.
     */
    boolean mIsUserSeekable = true;

    /**
     * On key presses (right or left), the amount to increment/decrement the
     * progress.
     */
    private int mKeyProgressIncrement = 1;

    private static final int NO_ALPHA = 0xFF;
    private float mDisabledAlpha;

    private void updateThumbSize()
    {
    	this.thumbHeight = (this.getHeight() / 10);
    	this.thumbWidth = this.getWidth() - 20;
    }
    
    /*
     * Sets colors for thumb and background and some other fields.
     * */
    private void initializeFields()
    {
    	this.thumbPaintNormal.setColor(Color.BLACK);
    	this.thumbPaintNormal.setAlpha(40);
    	this.thumbPaintNormal.setAntiAlias(true);
    	
    	this.thumbPaintFocused.setColor(Color.WHITE);
    	this.thumbPaintFocused.setAlpha(150);
    	this.thumbPaintFocused.setAntiAlias(true);
    	
    	this.thumbPaintFocused_Stroke.setStyle(Paint.Style.STROKE);
    	this.thumbPaintFocused_Stroke.setStrokeWidth(3);
    	this.thumbPaintFocused_Stroke.setColor(Color.WHITE);
    	this.thumbPaintFocused_Stroke.setAntiAlias(true);
    	
    	this.thumbPaintNormal_Stroke.setStyle(Paint.Style.STROKE);
    	this.thumbPaintNormal_Stroke.setStrokeWidth(2);
    	this.thumbPaintNormal_Stroke.setColor(Color.BLACK);
    	this.thumbPaintNormal_Stroke.setAntiAlias(true);

    	this.updateThumbSize();  
    	
        paintBackground.setColor(Color.LTGRAY);
        paintGraphAxis.setColor(Color.BLACK);
        paintGraphAxis.setAntiAlias(true);
        paintGraphAxis.setTextSize(8);
        
        widthOneSign = this.paintGraphAxis.measureText("0");
        
        paintTopicCircles.setStyle(Paint.Style.STROKE);
        paintTopicCircles.setColor(Color.RED);
        paintTopicCircles.setAntiAlias(true);
        
        
        paintAverageMeterCurve.setStyle(Paint.Style.STROKE);
        paintAverageMeterCurve.setColor(Color.BLACK);
        paintAverageMeterCurve.setAntiAlias(true);
        
        paintLocalMeterCurve.setStyle(Paint.Style.STROKE);
        paintLocalMeterCurve.setColor(Color.RED);
        paintLocalMeterCurve.setAntiAlias(true);
        
        paintthumbGraphMode_Stroke.setStyle(Paint.Style.STROKE);
        paintthumbGraphMode_Stroke.setColor(Color.WHITE);
        paintthumbGraphMode_Stroke.setAntiAlias(true);
        
        paintThumbGraphMode.setColor(Color.RED);
        paintThumbGraphMode.setAlpha(150);
        paintThumbGraphMode.setAntiAlias(true);
        
        paintInactiveThumbGraphMode.setColor(Color.DKGRAY);
        paintInactiveThumbGraphMode.setAlpha(150);
        paintInactiveThumbGraphMode.setAntiAlias(true);
        
        paintTopicText.setColor(Color.BLACK);
        paintTopicText.setAntiAlias(true);
        paintTopicText.setTextSize(20);
    
        this.setProgress(50);
        this.numberFormat.setMinimumIntegerDigits(2);
        this.numberFormat.setMaximumIntegerDigits(2);
    }

    public MeterControl(Context context) 
    {
    	super(context);
    	initializeFields();
		
    }

    public MeterControl(Context context, AttributeSet attrs) 
    {
        super(context, attrs);
        initializeFields();
    }

    public MeterControl(Context context, AttributeSet attrs, int defStyle) 
    {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.SeekBar, defStyle, 0);
      
        a.recycle();

        a = context.obtainStyledAttributes(attrs,
                R.styleable.Theme, 0, 0);
        mDisabledAlpha = a.getFloat(R.styleable.Theme_android_disabledAlpha, 0.5f);
        a.recycle();
        
        this.initializeFields();
    }
    /**
     * Sets the amount of progress changed via the arrow keys.
     *
     * @param increment The amount to increment or decrement when the user
     *            presses the arrow keys.
     */
    public void setKeyProgressIncrement(int increment) 
    {
        mKeyProgressIncrement = increment < 0 ? -increment : increment;
    }

    /**
     * Returns the amount of progress changed via the arrow keys.
     * <p>
     * By default, this will be a value that is derived from the max progress.
     *
     * @return The amount to increment or decrement when the user presses the
     *         arrow keys. This will be positive.
     */
    public int getKeyProgressIncrement() 
    {
        return mKeyProgressIncrement;
    }

    @Override
    public synchronized void setMax(int max) 
    {
        super.setMax(max);

        if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) 
        {
            // It will take the user too long to change this via keys, change it
            // to something more reasonable
            setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
        }
    }

    @Override
    protected void drawableStateChanged() 
    {
        super.drawableStateChanged();

        
        Drawable progressDrawable = getProgressDrawable();
        if (progressDrawable != null) {
            progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
        }

       //draw thumb focused/unfocused
        int[] states = getDrawableState();
        
        this.drawThumbFocused = false;
        
        for (int state : states) {
            if (state == android.R.attr.state_pressed || state == android.R.attr.state_selected) 
            {
                this.drawThumbFocused = true;
                break;
            }
        }

        this.invalidate();
    }

    @Override
    void onProgressRefresh(float scale, boolean fromUser) 
    {  	
        setThumbPos(getHeight(), null, scale);
        invalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) 
    {
        this.myWidth = w;
        this.myHeight = h;
       
    	Drawable d = getCurrentDrawable();

        this.updateThumbSize();
        
        // The max height does not incorporate padding, whereas the height
        // parameter does
        int max = getMax();
        float scale = max > 0 ? (float) getProgress() / (float) max : 0;

      
            if (d != null) {
                // Canvas will be translated by the padding, so 0,0 is where we start drawing
                d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - mPaddingTop);
            }
            
          
         setThumbPos(h, null, scale);
         
         
         if(h < 0 || w < 0)
        	 return;
  
         if(h > 800 || w > 800) //large display
         {
        	 this.radiusTopicCircles = 10;
        	 this.radiusSmallThumb = 16;
        	 this.paintTopicText.setTextSize(30);
        	 this.offsetTopicTextToTop = 60;
        	 this.widthOneSymbolTopic = paintTopicText.measureText("E");
        	 this.offSetToCircle = 30;
        	 this.differenceTouch = 4;
         }
         else
         {
        	 this.radiusTopicCircles = 6;
        	 this.radiusSmallThumb = 8;
        	 this.paintTopicText.setTextSize(20);
        	 this.offsetTopicTextToTop = 30;
        	 this.widthOneSymbolTopic = paintTopicText.measureText("E");
        	 this.offSetToCircle = 15;
        	 this.differenceTouch = 2;
         }

         
         this.availableWidth = this.myWidth - (graphOffsetToLeft + graphOffsetToRight);
      

         if(h != oldh) //height changed
         {
        	 //recalculate all scaled y-axis values of graph
        	 for(MeterValueVisual currentValue : this.meterValues)
        	 {
        		 currentValue.valuePercentile10_YAxis = this.calculatePointYAxis(currentValue.getValuePercentile10());
        		 currentValue.valuePercentile90_YAxis = this.calculatePointYAxis(currentValue.getValuePercentile90());
        		 currentValue.valueLocalMeter_YAxis = this.calculatePointYAxis(currentValue.getValueLocalMeter());
        		 currentValue.valueAverage_YAxis = this.calculatePointYAxis(currentValue.getValueAverage());
        	 }
         }
         
         //update according to new width of control
         if(w != oldw) //width changed
         {
        	 this.calculateTopicsXAxisPoints();
        	 this.calculateGraphPaths();
         }
         
         this.invalidate();       
    }

    /**
     * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
     */
    private void setThumbPos(int h, Drawable thumb, float scale) 
    {
    	this.currentThumbValue = (int) (scale * this.getMax());
    	
    	int thumbHalf = thumbHeight /2;
    	
    	int newYValue = this.calculatePointYAxis((int)(scale * this.getMax()));
    	currentMeterValueYAxis = newYValue;
    	
    	float thumbPos = newYValue - thumbHalf;
                
        int gap = (this.getWidth() - this.thumbWidth) / 2;
        float leftBound = gap;
        
        
        // Canvas will be translated, so 0,0 is where we start drawing
        thumbRect.set(leftBound, thumbPos, leftBound + thumbWidth, thumbPos + thumbHeight);
        
        this.ensureThumbIsWithinGraphBounds();
    }

    /*
     * Checks that thumb is not too low or to high and corrects its position if necessary.
     * */
    private void ensureThumbIsWithinGraphBounds()
    {   	
    	if(this.thumbRect.top < 0)
    	{
    		this.thumbRect.offset(0, -1 * this.thumbRect.top);
    	}
    	
    	int lowestLine = this.myHeight - this.graphOffsetToBottom;
    	
    	if(this.thumbRect.top + this.thumbHeight > lowestLine)
    	{
    		this.thumbRect.set(this.thumbRect.left, lowestLine - this.thumbHeight, this.thumbRect.right, lowestLine);
    	}
    }

    /*
     * Returns the local meter value currently display by the thumb of the control.
     * */
    public int getThumbValue()
    {
    	return this.currentThumbValue;
    }

	
    /*
     * Assigns a meter value (0 - 100) to the control.
     * */
    public void setThumbValue(int value)
    {
    	if(this.historyModeActive) //do not allow changing local value in history mode
    		return;
    	
    	if(value > 100)
    	{
    		Log.e("AbsVerticalSeekBar.setCurrentMeterValue", "value > 100 not allowed");
    		return;
    	}
    
    	this.currentThumbValue = value;
    	this.setProgress(value);
    }
    
    @Override
    protected synchronized void onDraw(Canvas canvas) 
    {
          super.onDraw(canvas);
            
          if(!this.userIsTouchingThumb || this.inReadOnlyMode) //control is in mode where graph will be shown
          {
        	canvas.save();
          
          	//draw graph of meter values
          	canvas.clipPath(this.graphPercentilePath, Op.DIFFERENCE);
          	canvas.drawPaint(paintBackground);
  
          	canvas.restore();
            
          	drawAverageAndLocalMetersCurve(canvas);
          	drawGraphAxisLines(canvas);
          	drawTopicCircles(canvas);
          	
          	 drawCurrentTopicText(canvas);
          }
        
          if(!this.inReadOnlyMode)
        	  drawThumb(canvas); 
    }

    /*
     * Draws a circle on the x-axis for each topic.
     * */
	private void drawTopicCircles(Canvas canvas) 
	{
		if(this.topics_xAxisValues == null || this.topics_xAxisValues.length == 0)
			return;
		
		final int topicCount = this.allTopics.size();
		final int xAxis_y    = this.myHeight - this.graphOffsetToBottom - (this.radiusTopicCircles / 2);
		
		for(int i = 0; i < topicCount; i++)
		{
			if(this.topics_xAxisValues[i] > this.graphOffsetToLeft)
			{
				canvas.drawCircle(this.topics_xAxisValues[i], xAxis_y, this.radiusTopicCircles, this.paintBackground);
				canvas.drawCircle(this.topics_xAxisValues[i], xAxis_y, this.radiusTopicCircles, paintTopicCircles);				
			}
		}
	}

	/**
	 * Draws the thumb used to modify local meter value.
	 */
	private void drawThumb(Canvas canvas) 
	{
		if(this.userIsTouchingThumb)
		{
			//draw thumb
			if(this.drawThumbFocused)
			{

				//fill
				canvas.drawRoundRect(this.thumbRect, (float)5, (float)5, thumbPaintFocused);

				//stroke on top
				canvas.drawRoundRect(this.thumbRect, (float)5, (float)5, thumbPaintFocused_Stroke);
			}
			else
			{
				//fill
				canvas.drawRoundRect(this.thumbRect, (float)5, (float)5, thumbPaintNormal);

				//stroke on top
				canvas.drawRoundRect(this.thumbRect, (float)5, (float)5, thumbPaintNormal_Stroke);
			}
		}
		else //control is in mode where graph is shown
		{
			float x = this.myWidth - this.graphOffsetToRight - this.radiusSmallThumb + 2;
			
			if ((this.meterValues.size() > 0) && (this.meterValues.get(this.meterValues.size()-1).getValueLocalMeter() > -1))
			{
				canvas.drawCircle(x, this.currentMeterValueYAxis, this.radiusSmallThumb, paintThumbGraphMode);
			} else {
				canvas.drawCircle(x, this.currentMeterValueYAxis, this.radiusSmallThumb, paintInactiveThumbGraphMode);
			}
			
			if(this.currentMeterValueYAxis > 0.7 * this.myHeight) //point lies in red area -> draw white stroke for better visibility
				canvas.drawCircle(x, this.currentMeterValueYAxis, this.radiusSmallThumb, this.paintthumbGraphMode_Stroke);	
		}
	}

	/**
	 * Draws axis lines and text for axis of graph.
	 */
	private void drawGraphAxisLines(Canvas canvas) 
	{
          int   xAxis_y   	  = this.myHeight - this.graphOffsetToBottom;
          int drawableWidth = this.myWidth - this.graphOffsetToRight;
          
          //x-axis
          canvas.drawLine(this.graphOffsetToLeft, xAxis_y,
        		          drawableWidth, xAxis_y, paintGraphAxis);
            
          //y-axis
          canvas.drawLine(this.graphOffsetToLeft, this.graphOffsetToTop, this.graphOffsetToLeft, xAxis_y, paintGraphAxis);
          
          
          //captions for axis
          String minutesShown = "+ " + getCountOfMinutesShownInGraph() + " min";
          float minutesTextWidth = minutesShown.length() * widthOneSign;
          
          canvas.drawText(minutesShown, drawableWidth - minutesTextWidth - this.graphOffsetToRight, this.myHeight - widthOneSign, paintGraphAxis);
          canvas.drawText("100", this.graphOffsetToLeft - 5, this.graphOffsetToTop - 5, paintGraphAxis);
          
          if(this.meterValues.size() == 0)
          {
        	  canvas.drawText("0", this.graphOffsetToLeft,  this.myHeight - widthOneSign, paintGraphAxis);
        	  return;
          }

          Timestamp ts = new Timestamp(lastShownTimems);
          
          String timeString = this.numberFormat.format(ts.getHours()) + ":" + this.numberFormat.format(ts.getMinutes()) + ":" + this.numberFormat.format(ts.getSeconds());
          //	+" / "+String.valueOf(ts.getTime()-now.getTime()); 
          canvas.drawText(timeString, this.graphOffsetToLeft,  this.myHeight - widthOneSign, paintGraphAxis);
	}
    
	/*
	 * Draws graph curve for local meter values and average meter values.
	 * */
	private void drawAverageAndLocalMetersCurve(Canvas canvas)
	{
		canvas.drawPath(this.graphAverageValuesPath, this.paintAverageMeterCurve);
		canvas.drawPath(this.graphLocalValuesPath, this.paintLocalMeterCurve);
	}
    
	/*
	 * Draws the currently selected topic.
	 * */
	private void drawCurrentTopicText(Canvas canvas)
	{
		if(!this.drawTopicText)
			return;
		
		float textWidth = this.paintTopicText.measureText(this.currentTopicText);
		float x = (this.availableWidth - textWidth) / 2 + this.graphOffsetToLeft;
		
		canvas.drawText(this.currentTopicText, x, this.offsetTopicTextToTop, this.paintTopicText);
	}
	
    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    {
        Drawable d = getCurrentDrawable();

        int dw = 0;
        int dh = 0;
        if (d != null) {
            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
            dw = Math.max(thumbWidth, dh);
            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
        }
        dw += mPaddingLeft + mPaddingRight;
        dh += mPaddingTop + mPaddingBottom;

        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
                resolveSize(dh, heightMeasureSpec));
    }

    /*
     * React to event when graph is clicked.
     * Only fires if control is in readonly mode and no topic-circle is clicked.
     * */
    public void setOnGraphClicked(OnClickListener handler)
    {
    	this.graphClicked = handler;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mIsUserSeekable || !isEnabled()) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                setPressed(true);
             
                this.oldTouchX = event.getX();
                
                //check whether user clicked one of topic circles
                int indexClickedTopic = this.getIndexClickedTopic(this.oldTouchX, event.getY()); 
                if( indexClickedTopic != -1 )
                {
                	onTopicClicked(indexClickedTopic);
                	return false;
                }
                
                if(this.inReadOnlyMode && !this.historyModeActive) // do not react to touch if control is in read only mode
                {
                	if(this.graphClicked != null)
                		this.graphClicked.onClick(this);
                	
                	return true;
                }
                onStartTrackingTouch();
                trackTouchEvent(event);
                break;

            case MotionEvent.ACTION_MOVE:
            	 if(this.inReadOnlyMode  && !this.historyModeActive) // do not react to touch if control is in read only mode
                 	return true;
                trackTouchEvent(event);
                attemptClaimDrag();
                break;

            case MotionEvent.ACTION_UP:
            	 if(this.inReadOnlyMode  && !this.historyModeActive) // do not react to touch if control is in read only mode
                 	return true;
            	 
                this.oldTouchX = -1;
                
                trackTouchEvent(event);
                onStopTrackingTouch();
                setPressed(false);
                // ProgressBar doesn't know to repaint the thumb drawable
                // in its inactive state when the touch stops (because the
                // value has not apparently changed)
                invalidate();
                break;

            case MotionEvent.ACTION_CANCEL:
            	 if(this.inReadOnlyMode  && !this.historyModeActive) // do not react to touch if control is in read only mode
                 	return true;
            	 
            	this.oldTouchX = -1;
            	 
                onStopTrackingTouch();
                setPressed(false);
                invalidate(); // see above explanation
                break;
        }
        return true;
    }
    
	private void trackTouchEvent(MotionEvent event) 
    { 
		if(this.historyModeActive)
		{
			this.trackTouchEventHistoryMode(event);
			return;
		}
		
		if(this.inReadOnlyMode)
			return;
		
        final int height = getHeight();
        final int available = height - this.graphOffsetToBottom - this.graphOffsetToTop;
        int y = height - (int)event.getY();
        float scale;
        float progress = 0;
        if (y < this.graphOffsetToBottom) {
            scale = 0.0f;
        } else if (y > height - this.graphOffsetToTop) {
            scale = 1.0f;
        } else {
            scale = (float)(y - this.graphOffsetToBottom) / (float)available;
            progress = 0; //mTouchProgressOffset;
        }

        final int max = getMax();
        progress += scale * max;

        setProgress((int) progress, true);
    }

	/*
	 * Method needed to allow switch displayed time in history mode of control with touch gestures (left / right) 
	 */
	private void trackTouchEventHistoryMode(MotionEvent event) 
    {
	    int action = event.getAction();

	    if (action == MotionEvent.ACTION_UP) {
			this.history_oldTouchX = -1;
	    }

	    if (action == MotionEvent.ACTION_DOWN) {
			this.history_oldTouchX = event.getX();
	    }

	    if (action == MotionEvent.ACTION_MOVE) 
	    {
			float difference = this.history_oldTouchX - event.getX();
			
			difference *= differenceTouch;
			
			long timeDiff = (long)((difference / (float)this.availableWidth) * (float)this.countOfmsShownInGraph);
			
			this.setLastShownTime(this.lastShownTimems + timeDiff);
			
			this.history_oldTouchX = event.getX();
	    }
	}
	
	public void setLastShownTime(long newLastShownTime)
	{
		// crop to available values if in history mode
		if ((this.historyModeActive) && (meterValues.size() > 0))
		{
			newLastShownTime = Math.max(meterValues.get(0).getTimeMS(), newLastShownTime);
			newLastShownTime = Math.min(meterValues.get(meterValues.size()-1).getTimeMS(), newLastShownTime);
		}
		this.lastShownTimems = newLastShownTime; 
		if (!this.currentlyUpdatingValues)
			redraw();
	}
    
	/**
     * Tries to claim the user's drag motion, and requests disallowing any
     * ancestors from stealing events in the drag.
     */
    private void attemptClaimDrag() 
    {
    	if(this.inReadOnlyMode  && !this.historyModeActive)
    		return;
    	
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(true);
        }
    }

    /**
     * This is called when the user has started touching this widget.
     */
    void onStartTrackingTouch() 
    {
    	userIsTouchingThumb = true;
    	this.invalidate();
    }

    /**
     * This is called when the user either releases his touch or the touch is
     * canceled.
     */
    void onStopTrackingTouch() 
    {
    	userIsTouchingThumb = false;
    	this.invalidate();
    }

    /**
     * Called when the user changes the seekbar's progress by using a key event.
     */
    void onKeyChange() {
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) 
    {
    	if(this.inReadOnlyMode)
    		return true;
    	
        int progress = getProgress();

        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_DOWN:	
                if (progress <= 0) break;
                setProgress(progress - mKeyProgressIncrement, true);
                onKeyChange();
                return true;

            case KeyEvent.KEYCODE_DPAD_UP:
                if (progress >= getMax()) break;
                setProgress(progress + mKeyProgressIncrement, true);
                onKeyChange();
                return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    /*
     * Sets control in history-view mode:
     *  -> readonly
     *  -> allow to view all data without respect to time
     */
    public void setControlInHistoryMode()
    {
    	this.historyModeActive = true;
    	this.setControlInReadOnlyMode();
    	
    	this.beginUpdatingMeterValues();
    	this.setCountOfMinutesShownInGraph(15); //default
    	
    	//re-draw graph
    	this.endUpdatingMeterValues();
    }
    
    public boolean isInHistoryMode()
    {
    	return this.historyModeActive;
    }
    
    /*
     * If a topic was clicked its index (in allTopics ArrayList) is returned.
     * Returns -1 if no topic was clicked.
     * */
    private int getIndexClickedTopic(float x, float y)
    {
    	
    	
    	if(this.userIsTouchingThumb || this.allTopics.size() == 0 || (y < this.myHeight - this.graphOffsetToBottom - (this.radiusTopicCircles + offSetToCircle)))
    		return -1;
    	
    	
    	final int count = this.topics_xAxisValues.length;
    	
    	for(int i = 0; i < count; i++)
    	{
    		//check if user clicked near topic circle
    		if(x > (this.topics_xAxisValues[i] - (this.radiusTopicCircles + offSetToCircle)) && x < (this.topics_xAxisValues[i] + this.radiusTopicCircles + offSetToCircle))
    			return i;
    	}
    	
    	return -1;
    }
    
    /*
     * Shows a label with the text of the clicked topic.
     */
    private void onTopicClicked(int indexClickedTopic) 
    {
    	Log.d("onTopicClicked", "index: " + indexClickedTopic);
    	
    	this.drawTopicText = true;
    	this.currentTopicText =  this.allTopics.get(indexClickedTopic).getTopicText();
    
    	//check that topic text is not too long
    	int countChars = (int) (this.availableWidth / this.widthOneSymbolTopic);
		
    	if(this.currentTopicText.length() > countChars) //shorten topic text
    	{
    		this.currentTopicText = this.currentTopicText.substring(0, countChars - 3) + "...";
    	}
    	
    	//setup timer to make label disappear after 30 seconds
    	this.topicTimer.removeCallbacks(topicDisappearsRunnable);
    	this.topicTimer.postDelayed(topicDisappearsRunnable, this.topicShowTime);
    	
    	this.invalidate();

	}

    //Needed for timer for topic-label
    private Runnable topicDisappearsRunnable = new Runnable() 
    {
    	   public void run() 
    	   {
    		   //after time elapsed don't show topic anymore
    		   MeterControl.this.drawTopicText = false;
    		   
    		   //don't let timer fire again
    		   MeterControl.this.topicTimer.removeCallbacks(topicDisappearsRunnable);
    		   
    		   MeterControl.this.invalidate();
    	   }
    };
    
	/*
	 * Call this method before adding some meter values to the control to prevent updating the graph after each new value.
	 * At the end of adding meter values call endUpdatingMeterValues().
	 * */
	public void beginUpdatingMeterValues()
	{
		this.currentlyUpdatingValues = true;
	}
	
	public void endUpdatingMeterValues()
	{
		this.currentlyUpdatingValues = false;
		//update graph
		redraw();
	}
	
    public int getCountOfMinutesShownInGraph()
    {
    	return this.countOfmsShownInGraph / 60000;
    }
    
    /*
     * Set the count of minutes shown in graph (for example 60 for showing data of the last hour).
     * */
    public void setCountOfMinutesShownInGraph(int minutesToShow)
    {
    	if(minutesToShow == getCountOfMinutesShownInGraph())
    		return;
    	
    	this.countOfmsShownInGraph = minutesToShow * 60 * 1000;
    	
    	//this.lastShownTimems = getLastMeterValueTime();

    	if(!this.historyModeActive)
    	{
    	}
    	else
    	{
    	}

        //re-draw
    	if(!this.currentlyUpdatingValues)
    	{
    		redraw();
    	}
    }
   
    /*
     * Add meter value to data for displaying the graph.
     * */
    public void addNewMeterValue(MeterValue newValue)
    {
    	if(this.historyModeActive)
    		return;
    	
    	MeterValueVisual meterValue = new MeterValueVisual(newValue);
    	
    	//calculate y-axis values according to current height of control
    	meterValue.valuePercentile10_YAxis = this.calculatePointYAxis(newValue.getValuePercentile10());
    	meterValue.valuePercentile90_YAxis = this.calculatePointYAxis(newValue.getValuePercentile90());
    	meterValue.valueLocalMeter_YAxis = this.calculatePointYAxis(newValue.getValueLocalMeter());
    	meterValue.valueAverage_YAxis = this.calculatePointYAxis(newValue.getValueAverage());
    	
    	this.meterValues.add(meterValue);
    	
    	if(!this.currentlyUpdatingValues) //if more than one value is added don't repaint for now
    	{
    		redraw();
    	}
    }
    
    public void redraw()
    {
    	this.calculateGraphPaths();
		this.invalidate();
    }
    
    /*
     * Adds new topic to graph.
     * Use beginUpdatingMeterValues and endUpdatingMeterValues to prevent graph-updates if more than one topic is be added.
     * */
    public void addNewTopic(Topic topicValue)
    {
    	this.allTopics.add(topicValue);
    	
    	if(!this.currentlyUpdatingValues)
    	{
    		redraw();
    	}
    }
    
    /*
     * Calculates to points needed to draw topic circles depending on current width of the control.
     * */
    private void calculateTopicsXAxisPoints()
    { 
		if(this.myWidth < 0)
			return;
		
   	    final int countTopics = this.allTopics.size();
   	    
    	this.topics_xAxisValues = new int[countTopics];

    	for(int i = 0; i <  countTopics; i++)
    	{
    		this.topics_xAxisValues[i] = this.calculatePointXAxis(this.allTopics.get(i).getTimeMS());
    	}
    }
    
    /*
     * Calculates point on y-axis for a value depending on the current height of the control.
     * */
    private int calculatePointYAxis(int valueToScale)
    {       
    	int height = this.myHeight - (graphOffsetToTop + graphOffsetToBottom);
    	
    	if(height < 0)
    		return 0;
   	
    	float scaledValue = (float)( (float)valueToScale / 100);
    	  	
    	
    	return (this.myHeight - (int)(((float) (height * scaledValue)) + graphOffsetToTop));
    }
    
    /*
     * Calculates point on x-axis for a value (unit: seconds) depending on the current width of the control.
     *(Used to draw meter values in graph and circles for topics.)
     * */
    private int calculatePointXAxis(long timeStamp)
    {
    	long meterMS = timeStamp - (this.lastShownTimems - this.countOfmsShownInGraph);
    	
    	return ((int) (this.availableWidth * ((float) ((float)meterMS / (float)this.countOfmsShownInGraph)) + this.graphOffsetToLeft));
		    
    }

   /*
     * Calculates the path used to display the meter-values graph (with 10percentile and 90percentile).
     * */
    private void calculateGraphPaths()
    {
    	if(availableWidth < 0)
    		return;
    	
    	if(this.meterValues.size() == 0 || this.currentlyUpdatingValues)
    		return;
    	
   	    final long timeAxisStartms = this.lastShownTimems-this.countOfmsShownInGraph;
   	    
   		ArrayList<MeterValueVisual> curMeterValues = new ArrayList<MeterValueVisual>();

   		MeterValueVisual currentMeterValue = null;
    	for(int i = this.meterValues.size()-1;  i >= 0; --i)
    	{		
    		currentMeterValue = this.meterValues.get(i);
    		if (currentMeterValue.getTimeMS() > this.lastShownTimems) continue;
    		if (currentMeterValue.getTimeMS() < timeAxisStartms) {
        		currentMeterValue.valueXAxis = this.calculatePointXAxis(timeAxisStartms);
        		curMeterValues.add(currentMeterValue);
    			break;
    		}
    		currentMeterValue.valueXAxis = this.calculatePointXAxis(currentMeterValue.getTimeMS());
    		curMeterValues.add(currentMeterValue);
    	}
    	
    	// Reset all pathes
    	this.graphPercentilePath.reset();
    	this.graphLocalValuesPath.reset();
    	this.graphAverageValuesPath.reset();

    	// Nothing to draw? => return
    	if (curMeterValues.isEmpty()) 
    		return; 

    	int rightBorder = calculatePointXAxis(this.lastShownTimems);
    	
    	// Start the graphs at the right border of the control!
    	currentMeterValue = curMeterValues.get(0);
		this.graphPercentilePath.moveTo(rightBorder, currentMeterValue.valuePercentile90_YAxis);
    	MeterValueVisual lastMeterValue = currentMeterValue;

   		//add lines for 90percentile
    	// iterating backwards in time!
    	// the graph shall be stepped. on old xaxis value new yvalue.. 
    	for(int i = 0; i < curMeterValues.size(); ++i)
    	{		
    		currentMeterValue = curMeterValues.get(i);
    		this.graphPercentilePath.lineTo(lastMeterValue.valueXAxis, currentMeterValue.valuePercentile90_YAxis);
    		this.graphPercentilePath.lineTo(currentMeterValue.valueXAxis, currentMeterValue.valuePercentile90_YAxis);
    		lastMeterValue = currentMeterValue;
    	}
    	//add vertical line at end of graph to connect 90percentile line with 10percentile line
		this.graphPercentilePath.lineTo(currentMeterValue.valueXAxis, currentMeterValue.valuePercentile10_YAxis);
   		// add lines for 10th percentile, iterating forwards in time!
		for(int i = curMeterValues.size()-2; i >= 0; --i)
    	{		
    		currentMeterValue = curMeterValues.get(i);
    		graphPercentilePath.lineTo(currentMeterValue.valueXAxis, lastMeterValue.valuePercentile10_YAxis);
    		graphPercentilePath.lineTo(currentMeterValue.valueXAxis, currentMeterValue.valuePercentile10_YAxis);
    		lastMeterValue = currentMeterValue;
    	}
    	// add vertical line at beginning of graph to connect 10percentile line with 90percentile line
		this.graphPercentilePath.lineTo(rightBorder, currentMeterValue.valuePercentile10_YAxis);
		this.graphPercentilePath.close();
		
		// average and local values
    	currentMeterValue = curMeterValues.get(0);
		this.graphAverageValuesPath.moveTo(rightBorder, currentMeterValue.valueAverage_YAxis);
    	this.graphLocalValuesPath.moveTo(rightBorder, currentMeterValue.valueLocalMeter_YAxis);
    	lastMeterValue = currentMeterValue;
		
    	for(int i = 0; i < curMeterValues.size(); ++i)
    	{		
    		currentMeterValue = curMeterValues.get(i);
    		if (currentMeterValue.getValueAverage() > -1)
    		{
        		if (lastMeterValue.getValueAverage() > -1)
        		{
        			this.graphAverageValuesPath.lineTo(lastMeterValue.valueXAxis, currentMeterValue.valueAverage_YAxis);
        		} else {
        			this.graphAverageValuesPath.moveTo(lastMeterValue.valueXAxis, currentMeterValue.valueAverage_YAxis);
        		}
    			this.graphAverageValuesPath.lineTo(currentMeterValue.valueXAxis, currentMeterValue.valueAverage_YAxis);
    		}
    		if (currentMeterValue.getValueLocalMeter() > -1)
    		{
        		if (lastMeterValue.getValueLocalMeter() > -1)
        		{
        			this.graphLocalValuesPath.lineTo(lastMeterValue.valueXAxis, currentMeterValue.valueLocalMeter_YAxis);
        		} else {
        			this.graphLocalValuesPath.moveTo(lastMeterValue.valueXAxis, currentMeterValue.valueLocalMeter_YAxis);
        		}
        		this.graphLocalValuesPath.lineTo(currentMeterValue.valueXAxis, currentMeterValue.valueLocalMeter_YAxis);
    		}
    		lastMeterValue = currentMeterValue;
    	}
    	
       	this.calculateTopicsXAxisPoints();
    	
    	//DEBUG
    	//long end = System.currentTimeMillis();
      	//Log.d("execution time Measure meter control", "time: " + (end - start) + " ms");
      	//DEBUG
    }

	/*
	 * Needed to load old values when screen orientation was changed
	 * */
	public void loadOldValues(ArrayList<MeterValueVisual> oldValues, ArrayList<Topic> oldTopics)
	{
		if(oldValues == null || oldValues.size() == 0)
			return;
		
		this.beginUpdatingMeterValues();
		
		this.meterValues = oldValues;
		
		if(oldTopics != null)
			this.allTopics = oldTopics;
		
		this.endUpdatingMeterValues();    
		
		int oldLocalMeterValue = oldValues.get(oldValues.size() - 1).getValueLocalMeter();
		this.setThumbValue(oldLocalMeterValue);
	}

	/*
	 * Needed to get old values before screen orientation is changed
	 * */
	public  Object getCloneOfAllMeterValues()
	{
		return this.meterValues.clone();
	}

	/*
	 * Needed to get old values before screen orientation is changed
	 * */
	public  Object getCloneOfAllTopics()
	{
		return this.allTopics.clone();
	}
	
	/*
	 * Set control in read only mode - local meter value cannot be changed.
	 * */
	public void setControlInReadOnlyMode()
	{
		this.inReadOnlyMode = true;
	}

}
